VBcrackme 解析手引き「VBCrack3」
始める前に、補助ツール ExDec(VB DeComplier)で、P-Codeリストは作成してますね? では、WKTVBDE を起動します。 VBCrack3.exe を読込んで下さい。 すばやく「Action」→「Run」又は、ツールバーの「Run」で実行します。 WKTVBDebuggerのウィンドウと、「VB Crack Me #3」のウィンドウが表示されます。 次に、ブレークポイントの設定に入ります。 「Form Manager (Ctrl+F)」で、Form Managerウィンドウを表示します。 コンボボックスの▼ボタンをクリックして、「frmMain」を選択して下さい。 すると、「VB Crack Me #3」ウィンドウで使用されてるコントロールのボタンが有効になります。 (「Label」、「TextBox」、「Command」の3つ) 「Command」ボタンをクリックします。 ウィンドウが、ポップアップします。 コンボボックスの▼ボタンをクリックすると、「cmdOK」と「cmdQuit」と表示されますので、 「cmdOK」を選択します。 すると、いろいろと「cmdOK」ボタンに関する情報が表示されます。 ここで、「BPX」のボタンをクリックします。 一応、ブレークポイントが有効か確認してみましょう。 「On Execution (Ctrl+E)」をクリックします。 「Current BPX and BPM List」ウィンドウが表示されます。 確認できましたか? もし邪魔であれば、「Form Maneger」のウィンドウを閉じて構いません。 (閉じるは、「×」ボタンクリック) この「Current BPX and BPM List」ウィンドウも閉じて構いません。 フェーク・シリアル「12345-67890-abcde-fghij」と入力して、「登録」ボタンをクリックします。 (シリアル入力時に、1度 WKTVBDEがアクティブになります。 「Go!(F5)」で実行を続けてください。) すると、0x4027E0でブレークし、P-Codeリストが表示されます。 4027E0: 1B LitStr: '不正シリアルです。' と表示されてます。 (Win9x系OSでは、文字化けしてると思います。) P-Code(ExDec)リストの 「402800: 0d VCallHresult get__ipropTEXTEDIT」辺りで、入力シリアルの読込みをしていると思われます。 ぉ! WKTVBDEのP-CodeとExDecのリストが、違う事に気づきましたか? タイミング的に、ExDecのP-Codeがトレースするのに適していると思われますので、ExDecのP-Code表記にしてあります。 あくまでも独断です。 アドレス 0x402800 まで実行します。 ここでスタック域の先頭4バイトに注目します。 私の環境では、0x63F358っとなってます。 注:スタック域のアドレスなので環境により変わります。 表示されてる値に読替えてください。 1ステップ実行します。 (「Step Trace(F8)」) 0x63F358の内容を見てみましょう。(「Memory Dump(Ctrl+M)」を使用します。) 先頭4バイトにアドレス値(私の環境では、0x42B868)が入っています。 さらに、0x42B868 の内容を参照します。 入力シリアル(先頭のテキスト・ボックス)の文字が入っています。(UNICODE形式) WKTVBDEのP-Codeリストを見ます。 402805: f5 LitI4: -> 0x1 1 40280A: 6c ILdRf 0042B868h ← 入力シリアルが入っているアドレス値 40280D: 0b ImpAdCallI2 rtcLeftCharBstr rtcLeftCharBstrは、VB記述のLeft$()関数、引数は、1と、入力シリアル文字です。 この3行で、先頭入力シリアルの1文字を取得しています。 アドレス 0x402812 まで実行します。 すると、ログ表示部分に「FStStrNoPop -> '1'」と表示されます。(Left$の結果) また、WKTVBDEのP-Codeリストを見ます。 402815: 1b LitStr: 'K' 402818: Lead0/30 EqStr Left$の結果と、文字列の'K'との比較ですね。 アドレス 0x40281A まで実行します。 この時のスタック域の4バイトが、EqStrの比較結果です。 結果は、'1'と'K'を比較しているので、0(ゼロ)-falseです。 この値を、0xffffffff-true に変更します。 変更方法は、「Memory Dump(Ctrl+M)」で、スタックアドレスを入力して 先頭の値 Wクリック後に、4バイト 0xffに変更後、「Patch Now!」です。 またまた、WKTVBDEのP-Codeを見ると、数行下に 402834: 0d VCallHresult get__ipropTEXTEDIT が見つかります。 あ! ExDecのP−codeリストと同じですね。 入力シリアルの読込みのようですね。 アドレス 0x402834 まで実行します。 この時のスタック域4バイトのアドレスが、0x63F348となってます。 1ステップ実行し、0x63F348 の内容を参照すると、アドレス値(0x42E28C)が入っています。 0x42E28C の内容を参照すると、入力シリアル(先頭のテキスト・ボックス)の文字が入っています。 WKTVBDEのP-Codeを見ると、 402839: 6c ILdRf 0042ACB0h 40283C: 4a FnLenStr ← len(先頭のテキスト・ボックスの文字) : 文字数取得 40283D: f5 LitI4: -> 5h 5 402842: c7 EqI4 ← 定数5 と 40283C での文字数の比較 402843: c4 AndI4 ← 上の比較結果値と、シリアル先頭文字='K'の結果値のAnd 402858: 1c BranchF: 402B43 アドレス 402843 の結果が、0(false)の時、チェック処理の最後へジャンプ まとめ(その1) 「一番最初のシリアル入力域には、先頭が'K'で始まる5文字でね。」 先ほど、先頭文字'K'の比較結果は、true値に変更しました。 文字列の長さも5文字入力したので気にせず進めます。 では、アドレス 0x402844 まで実行します。 スタック域の4バイトは、AndI4の結果 0xffffffff(true)が入っています。 また、P-Code(ExDec)リストを見ると、 402875: 0d VCallHresult get__ipropTEXTEDIT 402894: 0d VCallHresult get__ipropTEXTEDIT の2箇所で、入力シリアルの読込みを行っています。 この読込み結果を見ていきます。 アドレス 0x402875 まで実行します。 この時の、スタック域4バイトのアドレス値(0x63F358)をメモして1ステップ実行し、 0x63F358 の内容を参照すると、アドレス値(0x42B868)が入っています。 0x42B868 の内容を参照すると、入力シリアル(先頭のテキスト・ボックス)の文字ですね。 次に、アドレス 0x402894 まで実行します。 また、スタック域4バイトのアドレス値(0x63F348)をメモして1ステップ実行し、 0x63F348 の内容を参照すると、アドレス値(0x42AF38)が入っています。 0x42AF38 の内容を参照すると、これも入力シリアル(先頭のテキスト・ボックス)の文字ですね。 402899: 28 LitVarI2: 1h, 1 40289E: f5 LitI4: -> 3h 3 4028A3: 6c ILdRf 0042AF38h 4028A6: 0b ImpAdCallI2 rtcMidCharBstr ここで、VB表記 Mid$("12345", 3, 1) {"12345"の文字列から3文字目の1文字抽出} アドレス 0x4028AB まで実行します。 ログ表示部分に「FStStrNoPop -> '3'」と表示されます。(Mid$の結果) カレント行の「FStStrNoPop 0063F304h」のスタックアドレス値と、オペランドコード 23 00 FF の 相対オフセット値 0xFF00 をメモしといてください。後ほど使います。 4028AE: 0a ImpAdCallFPR4: rtcR8ValFromBstr で、 Mid$の結果'3'を数値に変換します。 変換後の数値形式は、倍精度浮動小数点(8バイト)です。 アドレス 0x4028B3 まで実行します。 ここで変換後の値は入るアドレスが、WKTVBDE には表示されてません。(謎です) では、結果が入るアドレスを計算してみましょう。 先程、メモしたスタックアドレス値とオペランドコードの相対オフセット値を用いて計算してみましょう。 まず、スタック域の先頭と思われるアドレスが、WKTVBDE のタイトルバーの下、 「Locs. Addr; xxxxxxxxh」に表示されてる値であると思います。 私の環境では、スタック先頭アドレスは、0x63F3AC です。 0x63F3AC + 0xFFFFFF00(相対オフセット値) = 0x63F2AC です。 実際は、0x63F304 と 計算結果より 0x58 バイト大きいアドレスです。 何故 0x58 バイト大きいのかわかりません。 (解決できたら教えてください。) 悩んでいても進まないので、補正値として使ってしまいます。 では、FStFPR8 のアドレス値を算出します。 0x63F3AC + 0xFFFFFEE8 + 0x58 = 0x63F2EC となります。 1ステップ実行し、0x63F2EC の内容を参照してみましょう。 63F2EC: 00 00 00 00 00 00 08 40 ですね。 倍精度浮動小数点形式で、値 3.0です。 アドレス 0x63F2EC を以降 local_0118 と記します。 P-Codeリストを見ると 4028B6: 28 LitVarI2: 1h 1 4028BB: f5 LitI4: -> 2h 2 4028C0: 6c ILdRf 0042B868h → "12345" 4028C3: 0b ImpAdCallI2 rtcMidCharBStr ここで、VB表記 Mid$("12345", 2, 1) {"12345"の文字列から2文字目の1文字抽出} アドレス 0x4028C8 まで実行します。 ログ表示部分に「FStStrNoPop -> '2'」と表示されます。(Mid$の結果) 4028CB: 0a ImpAdCallFPR4: rtcR8ValFromBstr で、 Mid$の結果'2'を倍精度浮動小数点の数値に変換します。 アドレス 0x4028D0 まで実行します。 カレント行以降に 4028D0: f4 LitI2_Byte: -> 2h 2 4028D2: eb CR8I2 → 整数 2 を、倍精度浮動小数点に変換 4028D3: b3 MulR8 → 倍精度浮動小数点の掛算 ( Mid$の結果'2' × 2) 4028D4: f3 LitI2: -> 3E8h 1000 4028D7: eb CR8I2 → 整数 1000 を、倍精度浮動小数点に変換 4028D8: b3 MulR8 → アドレス 0x4028D3 の結果 × 1000 アドレス 0x4028D9 まで実行します。 4028D9: 6f FLdFPR8 local_0118 です。 local_0118 から内容を読込んでいます。 また、カレント行から数行掛算等の演算が続いてます。 4028DC: f4 LitI2_Byte: -> 4h 4 4028DE: eb CR8I2 → 整数 4 を、倍精度浮動小数点に変換 4028DF: b3 MulR8 → local_0118 × 4 4028E0: ab AddR8 → 上記の結果 + アドレス 04028D8 の結果 アドレス 0x4028E1 まで実行します。 4028E1: Lead2/CVarR8 このオペランドの相対オフセットから、スタックアドレスを算出します。 (0x63F2F4) 1ステップ実行し、0x63F2F4 の内容を参照すると、 0063F2F4: 05 00 00 00 00 00 00 00 ← バリアント変数のタイプ(倍精度浮動小数点) 0063F2FC: 00 00 00 00 00 58 AF 40 ← 値 今までの演算結果を、バリアント変数に変換です。 1ステップ実行し、また オペランドの相対オフセットから、スタックアドレスを算出します。 (0x63F36C) ここで、0x63F2F4 → 0x63F36C に、倍精度浮動小数点値を代入してます。 アドレス 0x63F36C を以降 local_0098 と記します。 アドレス 0x402920 : 0d VCallHresult get__ipropTEXTEDIT まで実行します。 スタック域にあるアドレス値を控えます。(0x63F358) 1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B82C)が入っています。 0x42B82C の内容を参照すると、入力シリアル(2番目のテキスト・ボックス)の文字ですね。 アドレス 0x402941 まで実行します。 すると、3行下に 「0040294A: 0B ImpAdCallI2 rtcbstrFromFormatVar」と表示されます。(VBのFormat文です。) さらに、アドレス 0x40294A まで実行します。 ここで、local_0098 の値を、'00000'形式の文字列にしてます。 1ステップ実行すると、ログ表示部分に「FStStrNoPop -> '04012'」と表示されます。 そして、なんとP-Code(WKTVBDE)リストの下のほうに、見慣れたAPIが・・・・ 402963: 5e ImpAdCallI4 kernel32!lstrcmpA があります。 アドレス 0x402963 まで実行します。 lstrcmpAの引数は、スタック域にあります。 0x42E28C → '67890', 0x42B818 → '04012', 5 フェークシリアルと正解シリアルの比較ですね。 lstrcmpAの結果を、「402974: c7 EqI4」でチェックしてます。 アドレス 0x402974 まで実行します。 lstrcmpAの結果=0がのチェックですね。 1ステップ実行して、スタック域の4バイトを、 0xffffffff (true)に変更します。 まとめ(その2) 「Kabcd-XXXXX とすると XXXXX = a * 2 * 1000 + b * 4 となります。」 P-Code(ExDec)のリストを見ます。 4029B5: 0d VCallHresult get__ipropTEXTEDIT 4029D4: 0d VCallHresult get__ipropTEXTEDIT の2箇所で、入力シリアルの読込みをしてますね。 また、何を読んでいるか見ていきましょう。 アドレス 0x4029B5 まで実行します。 ここでスタック域にあるアドレスをメモ(0x63F358) 1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B82C)が入っています。 0x42B82C の内容を参照すると、入力シリアル(先頭のテキスト・ボックス)の文字ですね。 アドレス 0x4029D4 まで実行します。 スタック域にあるアドレスをメモ(0x63F348) 1ステップ実行し、0x63F348 の内容を参照すると、アドレス値(0x42E28C)が入っています。 0x42E28C の内容を参照すると、またもや、入力シリアル(先頭のテキスト・ボックス)の文字ですね。 4029D9: 28 LitVarI2: 1h 1 4029DE: f5 LitI4: -> 5h 5 4029E3: 6c ILdRf 0042E28Ch → "12345" 4029E6: 0b ImpAdCallI2 rtcMidCharBstr → Mid$("12345", 5, 1)='5' 4029EB: 23 FStStrNoPop 4029EE: 0a ImpAdCallFPR4: rtcR8ValFromBstr → Mid$("12345", 5, 1)の結果を倍精度浮動小数点に変換 4029F3: 74 FStFPR8 アドレス 0x4029F3 まで実行します。 ここの時にまた、相対オフセットからスタックアドレスを計算しますが、オフセット値 0xFEE8 さっき計算しましたね。 local_0118 と同じアドレスです。 4029F6: 28 LitVarI2: 1h , 1 4029FB: f5 LitI4: -> 4h 4 402A00: 6c ILdRf 0042E28Ch → "12345" 402A03: 0b ImpAdCallI2 rtcMidCharBstr → Mid$("12345", 4, 1)='4' 402A08: 23 FStStrNoPop 402A0B: 0a ImpAdCallFPR4: rtcR8ValFromBstr → Mid$("12345", 4, 1)の結果を倍精度浮動小数点に変換 アドレス 0x402A0B まで実行します。 次からは、演算の固まりですね。 402A10: f4 LitI2_Byte: 19h 25 402A12: eb CR8I2 → 25を倍精度浮動小数点に変換 402A13: b3 MulR8 → Mid$の結果(4) × 25 402A14: 6f FLdFPR8 local_0118 の読込み 402A17: ab AddR8 → local_0118 + 402A13 の結果 402A18: e5 CI2R8 → 倍精度浮動小数点を2バイトIntegerに変換 402A19: 70 FStI2 local_009A アドレス 0x402A19 まで実行します。 すると、 402A19: 70 FStI2 0063F36A -> 69h 105 っと表示されます。 Integer値が、0x68(105)で、0x63F36Aに代入されます。 下の3行は、ワーク領域の開放です。 4行目から、また演算が始まりますね。 402A39: 6b FLdI2 local_009A の読込み (0x69) 402A3C: eb CR8I2 → 0x69 を倍精度浮動小数点に変換 402A3D: f4 LitI2_Byte: -> 3h 3 402A3F: eb CR8I2 → 3 を倍精度浮動小数点に変換 402A40: b6 DivR8 → 0x69 ÷ 3 402A41: f4 LitI2_Byte: -> 7h 7 402A43: eb CR8I2 → 7 を倍精度浮動小数点に変換 402A44: b3 MulR8 → (0x69 ÷ 3) × 7 402A45: e5 CI2R8 → 402A44 の結果を、2バイトIntegerに変換 402A46: 70 FStI2 local_009A アドレス 0x402A46 まで実行します。 すると、 402A46: 70 FStI2 0063F36A -> f5h 245 っと表示されます。 Integer値が、0xf5(245)で、0x63F36Aに代入されます。 次に、 402A63: 0d VCallHresult get__ipropTEXTEDIT の入力シリアルの読込み部分を見てみましょう。 アドレス 0x402A63 まで実行します。 ここでいつもの用に、スタック域のアドレス値をメモ! (0x63F358) 1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B868)が入っています。 0x42B868 の内容を参照すると、入力シリアル(3番目のテキスト・ボックス)の文字ですね。 402A72: 3a LitVarStr: '00000' 402A77: 4e FStVarCopyObj local_00DC 402A7A: 04 FLdRfVar local_00DC 402A7D: 04 FLdRfVar local_009A → 0xf5(245) 402A80: 4d CVarRef: ( local_00CC ) 4002 402A85: 0b ImpAdCallI2 rtcBstrFromFormatVar → Format$('00000', 245) 402A8A: 23 FStStrNoPop local_00BC と、Format文ですね。 アドレス 0x402A8A まで実行します。 ログ表示部分に、「FStStrNoPop -> '00245'」っとFormat文の結果が表示されます。 ここでまた、例のAPIが・・・・・ 402A9E: 5e ImpAdCallI4 kernel32!lstrcmpA があります。 アドレス 0x402A9E まで実行します。 スタック域のアドレスから 0x42AF38 → 'abcde', 0x42B818 → '00245', 5 フェークシリアルと正解シリアルの比較ですね。 lstrcmpAの結果を、「402AAF: c7 EqI4」でチェックしてます。 アドレス 0x402AAF まで実行します。 lstrcmpAの結果=0がのチェックですね。 1ステップ実行して、スタック域の4バイトを、 0xffffffff (true)に変更します。 まとめ(その3) 「Kabcd-XXXXX-YYYYY とすると YYYY = (c * 25 + d) / 3 * 7 となります。」 また、P-Code(ExDec)のリストを見ます。 402AC8: 04 FLdRfVar local_0098 → 4012 (倍精度浮動小数点) 402ACB: 6b FLdI2 local_009A → 0xf5(245) 402ACE: 44 CVarI2 local_00CC → 4012 (倍精度浮動小数点)を、2バイトIntegerに変換 402AD1: Lead0/94 AddVar local_00DC → 245 + 4012 402AD5: 28 LitVarI2: 2710h 10000 402ADA: Lead0/a4 ModVar → (245 + 4012) Mod 10000 402ADE: Lead1/f6 FStVar local_0098 アドレス 0x402ADE まで実行します。 もう少しです。 頑張りましょう! ここで、相対オフセット値からスタックアドレスを算出します。 (0x63F36C) 1ステップ実行し、0x63F36C の内容を参照すると、 0063F36C: 03 00 00 00 00 00 00 00 ← バリアント変数のタイプ(Long) 0063F374: A1 10 00 00 00 00 00 00 ← 値 次に、 402AFF: 0d VCallHresult get__ipropTEXTEDIT で読込んでいる入力シリアルの読込み部分を見てみましょう。 アドレス 0x402AFF まで実行します。 ここで、スタック域のアドレス メモしておきましょう。 (0x63F358) 1ステップ実行し、0x63F358 の内容を参照すると、アドレス値(0x42B868)が入っています。 0x42B868 の内容を参照すると、入力シリアル(4番目のテキスト・ボックス)の文字ですね。 402B11: 3a LitVarStr: '0000' 402B16: 4e FStVarCopyObj local_00DC 402B19: 04 FLdRfVar local_00DC 402B1C: 04 FLdRfVar local_0098 → 0x1a1 (0x63F36C) を参照 402B1F: 0b ImpAdCallI2 rtcBstrFromFormatVar → Format$('0000', 0x1a1) 402B24: 23 FStStrNoPop local_00B0 と、Format文です。 アドレス 0x402B24 まで実行します。 次のオペランドが、「30 EqStr」となってますね。 文字列の比較ですね。 1ステップ実行します。 ここで、スタック域にアドレスが2つあります。 1つ目は、0x42B818 → "4257" UNICODE形式 正解シリアル 2つ目は、0x42B868 → "fghij" UNICODE形式 フェークシリアル 1ステップ実行します。 スタック域に、比較結果 0(false)が入ります。 また、スタック域4バイトを、0xffffffff(true)に変更します。 あとは、思いっきり、「Go!(F5)」です。 まとめ(その4) 「Kabcd-XXXXX-YYYYY-ZZZZ とすると ZZZZ = (XXXXX + YYYYY) Mod 10000 となります。」 今回の正解シリアル値は、 K2345-04012-00245-4257 です。 大変長い説明になりましたが、これで終わりです。 次回は、講義するまえに「VBCrack1」のパッチイメージ募集します。 キャプテン・ジャックの指令:「次回までにナグ消せ!」「連絡方法は、追って板に打ちつける。」 と受信しました。∠(  ̄(工) ̄)